<?php
/**
 */
class Yz_Action_Select
    extends Yz_Action_Abstract
{

    /**
     *  @var array( fieldName: fieldSelectClause )
     */
    protected $fields = array();

    /** SQL rows limit, set to 1 for base Select to avoid occasionally selecting all rows
     *  @var int(1)
     */
    protected $limit = 1;

    protected $fieldStack = array();
    protected $substAlias = array();
    protected $fieldStackString;

    /************************************
     *          Public methods          *
     ************************************/


    /**
     *  @return Yz_Hash
     */
    public function fetch()
    {
        if (! $this->executed)
            throw new Yz_Action_Exception_NotExecutedYet();
        $values = $this->statement->fetch();
        if (! $values)
            throw new Yz_Action_Exception_NoValues();
        return $values;
    }



    /************************************
     *        Protected methods         *
     ************************************/

    /**
     *  Apply the arguments to the SQL statement
     *      Setup limit to avoid occasional selection of all rows
     *  @param Yz_SQL_Statement $sql
     *  @return this
     */
    protected function applyArguments(Yz_SQL_Statement $sql)
    {
        debug();
        if ($this->limit)
            $sql->limit($this->limit);
        return $this;
    }

    /**
     *  Common SELECT statement creator:
     *      the common part of statement
     *  @return Yz_SQL_Statement_Select
     */
    protected function buildStatement()
    {
        debug();
        $this->inspectModel();
        return Yz_SQL_Statement_Select::create()
            ->select($this->fields)
            ->from($this->tables)
        ;
    }



    /**
     *  Inspect model's property and create a field clause or
     *      recursively inspect referenced model
     *  @param Yz_Model_Interface $model
     *  @param string $alias
     *  @param string $propName
     *  @param Yz_Property_Interface $prop
     *  @return this
     */
    protected function inspectField(Yz_Model_Interface $model, $alias, $propName, Yz_Property_Interface $prop)
    {
        debug("Field: {$alias}.{$propName}");

        $parent = $model->getParentModel();
        if ($parent and $parent->offsetExists($propName)) {
            debug("Skipping property {$propName} which exists in parent {$parent}");
            // do nothing
        } else if ($prop instanceof Yz_Property_Interface_Reference) {
            $this->inspectReferenceField($model, $alias, $propName, $prop);
        } else {

            // TODO check custom settings for the field
            // use $modelName as table alias
            // prefix field aliases with `$modelName_`
            $f = "{$alias}_{$propName}";
            $clause = "{$alias}.{$propName}";

            $clause
                and $this->fields[$f] = $clause;
        }
        return $this;
    }

    /**
     *
     */
    protected function inspectReferenceField(
            Yz_Model_Interface $origModel,      // original model (to which the field belongs)
            $alias,
            $propName,
            Yz_Property_Interface $prop)
    {

        debug("Ref Field: {$alias}.{$propName}");
        $refModel = $prop->getModel();


        if ($prop instanceof Yz_Property_Interface_ReferenceList) {
            // the property is reference to a list (1-to-M, M-to-M)
            // the field is not physically present in the original Model's table
            // the values list will require a separate select
            $propName = null;
        } else {

            debug("Ref Model: {$refModel}");

            if ($origModel === $refModel) {
                // reference to self
                $propName .= 'Id';
                $this->fields[$alias . '_' . $propName] = "{$alias}.{$propName}";
            } else {
                $refAlias = $this->enterProperty($propName);

                if ($prop instanceof Yz_Property_OneToOne) {
                    // models are joined by id
                    // add two field clauses: for "my" id and for "parent's" id
                    // which have the same values
                    $propName = 'id';
                    $this->fields[$alias . '_' . $propName] = "{$alias}.{$propName}";
                } else {
                    // the property is a foreign key
                    $propName .= 'Id';
                }
                $this
                    ->addSource($origModel, $alias, $propName, $refModel, $refAlias)
                    ->doInspectModel($refModel, $refAlias)
                ;
                $this->exitProperty();
            }

        }
        return $this;
    }



    /** Add a table to table list using info from property
     *  @param $origModel, $origField, $refModel
     *  NOTE: the resulting origField will have 'Id' added (eg. type -> typeId)
     */
    protected function addSource($origModel, $origAlias, $origField, $refModel, $refAlias)
    {
        debug("Add {$refModel} as {$refAlias}");
        $key       = 'id';                          // use referenced model's primary key field name
        $refTable  = $refModel->getSourceName();       // ref.model table name
        // TODO
        //  if prop.allowNull : use LEFT JOIN
        //  otherwise, use INNER JOIN
        $table = Yz_SQL_Join::leftJoin($refTable, $refAlias)
            ->joinOn("{$refAlias}.{$key}={$origAlias}.{$origField}")
        ;
        $this->tables[$refAlias] = $table;

        return $this;
    }


    /** Enter a property into stack
     *
     */
    protected function enterProperty($propName)
    {
        $this->fieldStack[] = ucfirst($propName);
        return $this->fieldStackString = join('_', $this->fieldStack);
    }

    /** Pop last entered property off stack
     *
     */
    protected function exitProperty()
    {
        array_pop($this->fieldStack);
        $this->fieldStackString = join('_', $this->fieldStack);
        return $this;
    }


}
